'use strict';

const semver = require('semver');
const userHome = require('user-home');
const fs = require('fs')
const path = require('path')
const glob = require('glob')
const inquirer = require('inquirer')
const fse = require('fs-extra')
const ejs = require('ejs')
const Command = require('@cli-dev/command')
const Package = require('@cli-dev/package');
const log = require('@cli-dev/log')

const getProjectTemplate = require('./getProjectTemplate')
const { sleep,spinnerStart,exec:spawn, execAsync } = require('@cli-dev/utils');
const { rejects } = require('assert');



const TYPE_PROJECT = 'project';
const TYPE_COMPONENT = 'component';

const TEMPLATE_TYPE_NORMAL = 'normal';
const TEMPLATE_TYPE_CUSTOM = 'custom'

const WHITE_COMMAND = ['cnpm','npm'] //白名单命令

class InitCommand extends Command {
  // TODO:commander9.x版本，不能通过this._cmd.force方式拿到force,待处理
  init(){
   this.projectName = this._argv[0] || '';
   this.force = !!this._cmd.force; // 这里
   console.log(this._cmd.force,'this._cmd===222')
  }
 async exec(){
    try{
      // 1.准备阶段
   
   const projectInfo = await  this.prepare()
   console.log('projectInfo=666')
    if(projectInfo){
      this.projectInfo = projectInfo;
      log.verbose('projectInfo',projectInfo)
      // 2.下载模版
     await this.downloadTemplate()
      // 3. 安装模版
      await this.installTemplate()
    }
    }catch(err){
      log.error(err.message)
      if (process.env.LOG_LEVEL === 'verbose') {
        console.log(e);
      }
      
    }
  }

  checkCommand(cmd){
    if(WHITE_COMMAND.includes(cmd)){
      return cmd
    }
    return null;
  }


  async installTemplate(){
    /**
     * 1. 判断模版是否存在
     * 2. 判断是否是自定义模版，
     *  2.1 - 是自定义模版，查询自定义模版入口文件，
     *      2.1.2 判断入口文件是否存在，存在，执行入口文件，之后结束流程，不存在，终止执行，结束流程
     *  2.2 - 不是自定义模版，则是标准模版，
     *     2.2.1 - 拷贝模版代码到当前目录
     *     2.2.2 生成ejs渲染忽略文件目录
     *     2.2.3 ejs模版渲染
     *     2.2.4 执行启动命令
     *     2.2.2 结束
     */

    if(this.templateInfo){
      if(!this.templateInfo.type){
        this.templateInfo.type = TEMPLATE_TYPE_NORMAL
      }
      if( this.templateInfo.type === TEMPLATE_TYPE_NORMAL){
        // 标准安装
        this.installNormalTemplate()
      }else if( this.templateInfo.type = TEMPLATE_TYPE_CUSTOM){
          // 自定义安装
          this.installCustomTemplate()
      }else {
        throw new Error('项目类别无法识别')
      }
    }else {
      throw new Error('项目模版不存在！')
    }
    console.log(this.templateInfo,'this.templateInfo)=')
  }
  

 async execCommand(command, errMsg){
    let ret;
    if(command){
      const cmdArray = command.split(' ')
      let cmd =  this.checkCommand(cmdArray[0])
      if(!cmd){
        throw new Error('命令不存在！命令：' + command);
      }
      let args = cmdArray.slice(1)
      ret = await execAsync(cmd, args, {
        stdio: 'inherit',
        cwd: process.cwd()
      })
      if(ret !== 0){
        throw new Error(errMsg)
      }
    }
    return ret
  }


  async ejsRender(options) {
    const dir = process.cwd();
    const projectInfo = this.projectInfo;
    return new Promise((resolve, reject) => {
      glob('**', {
        cwd: dir,
        ignore: options.ignore || '',
        nodir: true,
      }, function(err, files) {
        if (err) {
          reject(err);
        }
        Promise.all(files.map(file => {
          const filePath = path.join(dir, file);
          return new Promise((resolve1, reject1) => {
            ejs.renderFile(filePath, projectInfo, {}, (err, result) => {
              if (err) {
                reject1(err);
              } else {
                fse.writeFileSync(filePath, result);
                resolve1(result);
              }
            });
          });
        })).then(() => {
          resolve();
        }).catch(err => {
          reject(err);
        });
      });
    });
  }

  async installNormalTemplate() {
    log.verbose('templateNpm', this.templateNpm);
    // 拷贝模板代码至当前目录
    let spinner = spinnerStart('正在安装模板...');
    await sleep();
    try {
      const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template');
      const targetPath = process.cwd();
      fse.ensureDirSync(templatePath);
      fse.ensureDirSync(targetPath);
      fse.copySync(templatePath, targetPath);
    } catch (e) {
      throw e;
    } finally {
      spinner.stop(true);
      log.success('模板安装成功');
    }
    const templateIgnore = this.templateInfo.ignore || [];
    // const ignore = ['**/node_modules/**', ...templateIgnore];
    const ignore = ['**/node_modules/**', 'public/**']
    await this.ejsRender({ ignore });
    const { installCommand, startCommand } = this.templateInfo;
    // 依赖安装
    // await this.execCommand(installCommand, '依赖安装失败！');
    // 启动命令执行
    // await this.execCommand(startCommand, '启动执行命令失败！');
  }

  async installCustomTemplate(){
    console.log('自定义模版')

  }
   
 

 async prepare(){
    /** 
     *  0. 判断项目模版是否存在
     *  1. 判断当前目录是否为空，
     *  2. 是否强制更新项目
        3. 询问是否需要继续创建项目/组件
        4. 获取项目基本信息 
     */

    const template = await getProjectTemplate();
    log.verbose('prepare===start',template)
     console.log('template===',template,)
    this.template = template
  

 
  // 1.1拿到当前的目录，判断是否为空
  const localPath = process.cwd()
  console.log(localPath,'localPath====') 
  if(!this.isDirEmpty(localPath)){ // 当前目录不为空，就询问形式问是否要创建项目
    let ifContinue = false
    if(!this.force){ // 如果force:true的话，就强制更新，不再询问
       ifContinue  = (await  inquirer.prompt({
        type: 'confirm',
        message:'当前文件夹不为空，是否继续创建项目？',
        name: 'ifContinue',
        default: false
      })).ifContinue
      if(!ifContinue){ // 选择了false,直接退出，return掉
        return;
      }
    }
   
    // 2. 是否强制更新项目
    if(ifContinue || this.force){ // 强制更新，清空当前目录
      // 清空之前，询问用户二次确认
      const { confirmDelete } = await  inquirer.prompt({
        type: 'confirm',
        message:'是否清空当前目录下的文件？',
        name: 'confirmDelete',
        default: false
      })
      if(confirmDelete){
        // 清空当前目录
        fse.emptyDirSync(localPath)
      }
    } 
  }
  //  3.4合成一个方法，获取项目基本信息， return项目的基本信息object
  return  this.getPorjectInfo()
 }

 async  downloadTemplate(){
   /**
    * 逻辑：
    *  1. 选择项目/组件模版
    *  2.生成Package对象 
    *   2.1 如果Package对象存在，则更新
    *  2.2 如果Package对象不存在，则安装Package
    *  3. 检查模版是否存在，存在则下载，不存在直接终止执行
    * 
    * === 技术实现
    * 1. 通过项目模版API获取项目模版信息
    * 1.1 通过egg.js搭建一套后端系统
    * 1.2 通过npm存储项目模版
    * 1.3 将项目模版信息存储到mongodb数据库中
    * 1.4 通过egg.js获取mongodb中的数据并且通过API返回
    * 4. 
    */
   const { projectTemplate } = this.projectInfo;
   const templateInfo = this.template.find(item => item.npmName === projectTemplate);
   const targetPath = path.resolve(userHome, '.cli-dev','template')
   const storeDir = path.resolve(userHome, '.cli-dev', 'template', 'node_modules')
   const {npmName, version } = templateInfo
   this.templateInfo = templateInfo
   const templateNpm = new Package({
    targetPath,
    storeDir,
    packageName: npmName,
    packageVersion: version,
  });
  if(! await templateNpm.exists()){
    const spinner = spinnerStart('正在下载模板...');
    await sleep()
    try{
      await templateNpm.install()
    }catch(err){
      throw err
    }finally{
    spinner.stop(true)
    if( templateNpm.exists()){
      log.success('下载模板成功')
      // 缓存模版信息
      this.templateNpm = templateNpm
    }
    }
  }else {
    const spinner = spinnerStart('正在更新模板...');
    await sleep()
    try{
      await templateNpm.update()
    }catch(err){
      if (await templateNpm.exists()) {
        log.success('更新模板成功');
        this.templateNpm = templateNpm;
      }
    }finally{
      spinner.stop(true);
        if (await templateNpm.exists()) {
          log.success('更新模板成功');
          this.templateNpm = templateNpm;
        }
    }
  }
 }
  async getPorjectInfo(){
  // 1.询问是否需要继续创建项目/组件
  // 2. 获取项目基本信息 
    // 输入name, version
    let projectInfo = {}
    function _isValidName(v){
      let reg = /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]* |   [_][a-zA-Z][a-zA-Z0-9]* | [a-zA-Z0-9])*$/;
      return reg.test(v)
    }
    const {type} = await inquirer.prompt({
      type:'list',
      name:'type',
      default: TYPE_PROJECT,
      choices:[{
        name: '项目',
        value: TYPE_PROJECT,
      },{
        name: '组件',
        value: TYPE_COMPONENT,
      }]
    })
    if(type === TYPE_PROJECT){
      // 项目的基本信息
      const 
      project = await inquirer.prompt([{
          type:'input',
          name: 'projectName',
          message:'请输入项目名称',
          default:'',
          validate: function(v){
             // Declare function as asynchronous, and save the done callback
             const done = this.async(); // inquirer库给出的方法，解决输入无效项目名称没有反应的体验,
            // 1. 首字母必须是英文字符
            // 2. 尾字母必须是英文或者数字，不能为字符
            // 3. 字符仅允"-_"
            // 合法： a, a_b, a-b, a-b-c, a_b_c, a-b1-c1, a_b1_c1,a1_b1_c1
            // 不合法：1, a_, a_1, a-1
             // Do async stuff
            setTimeout(function() {
              if (!_isValidName(v)) {
                done(`请输入合法的项目名称`);
                return;
              }
              done(null, true);
            }, 0);
          },
          filter:function(v){
            return v
          }
      },
      {
        type:'input',
        name: 'projectVersion',
        message:'请输入版本号',
        default:'1.0.0',
        validate: function(v){
          // 版本号的校验用semver库来实现
          const done = this.async();
          setTimeout(function() {
            if (!(!!semver.valid(v))) {
              done('请输入合法的版本号');
              return;
            }
            done(null, true);
          }, 0);
        },
        filter:function(v){
          if(!!semver.valid(v)){
            return semver.valid(v)
          }else {
            return v
          }
         
        } 
      },
      {
        type:'list',
        name:'projectTemplate',
        message:"请选择项目模版",
        choices: this.createTemplateChoice()

      }
    ])
    projectInfo = {
      type,
      ...project
    }
    }else {

    }
    // 生成classname
    if (projectInfo.projectName) {
      projectInfo.name = projectInfo.projectName;
      projectInfo.className = require('kebab-case')(projectInfo.projectName).replace(/^-/, '');
    }

    if(projectInfo.projectVersion){
      projectInfo.version = projectInfo.projectVersion
    }
    if(projectInfo.componentDescription){
      projectInfo.description = projectInfo.componentDescription;
    }
    
    return projectInfo
  }

  async getPorjectInfow() {
    function isValidName(v) {
      return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v);
    }

    let projectInfo = {};
    let isProjectNameValid = false;
    if (isValidName(this.projectName)) {
      isProjectNameValid = true;
      projectInfo.projectName = this.projectName;
    }
    // 1. 选择创建项目或组件
   
    const { type } = await inquirer.prompt({
      type: 'list',
      name: 'type',
      message: '请选择初始化类型',
      default: TYPE_PROJECT,
      choices: [{
        name: '项目',
        value: TYPE_PROJECT,
      }, {
        name: '组件',
        value: TYPE_COMPONENT,
      }],
    });
    log.verbose('type', type);
    this.template = this.template.filter(temp =>
      temp.tag.includes(type));
    const title = type === TYPE_PROJECT ? '项目' : '组件';
    const projectNamePrompt = {
      type: 'input',
      name: 'projectName',
      message: `请输入${title}名称`,
      default: '',
      validate: function(v) {
        const done = this.async();
        setTimeout(function() {
          // 1.首字符必须为英文字符
          // 2.尾字符必须为英文或数字，不能为字符
          // 3.字符仅允许"-_"
          if (!isValidName(v)) {
            done(`请输入合法的${title}名称`);
            return;
          }
          done(null, true);
        }, 0);
      },
      filter: function(v) {
        return v;
      },
    };
    const projectPrompt = [];
    if (!isProjectNameValid) {
      projectPrompt.push(projectNamePrompt);
    }
    projectPrompt.push({
        type: 'input',
        name: 'projectVersion',
        message: `请输入${title}版本号`,
        default: '1.0.0',
        validate: function(v) {
          const done = this.async();
          setTimeout(function() {
            if (!(!!semver.valid(v))) {
              done('请输入合法的版本号');
              return;
            }
            done(null, true);
          }, 0);
        },
        filter: function(v) {
          if (!!semver.valid(v)) {
            return semver.valid(v);
          } else {
            return v;
          }
        },
      },
      {
        type: 'list',
        name: 'projectTemplate',
        message: `请选择${title}模板`,
        choices: this.createTemplateChoice(),
      });
    if (type === TYPE_PROJECT) {
      // 2. 获取项目的基本信息
      const project = await inquirer.prompt(projectPrompt);
      projectInfo = {
        ...projectInfo,
        type,
        ...project,
      };
    } else if (type === TYPE_COMPONENT) {
      const descriptionPrompt = {
        type: 'input',
        name: 'componentDescription',
        message: '请输入组件描述信息',
        default: '',
        validate: function(v) {
          const done = this.async();
          setTimeout(function() {
            if (!v) {
              done('请输入组件描述信息');
              return;
            }
            done(null, true);
          }, 0);
        },
      };
      projectPrompt.push(descriptionPrompt);
      // 2. 获取组件的基本信息
      const component = await inquirer.prompt(projectPrompt);
      projectInfo = {
        ...projectInfo,
        type,
        ...component,
      };
    }
    // 生成classname
    if (projectInfo.projectName) {
      projectInfo.name = projectInfo.projectName;
      projectInfo.className = require('kebab-case')(projectInfo.projectName).replace(/^-/, '');
    }
    if (projectInfo.projectVersion) {
      projectInfo.version = projectInfo.projectVersion;
    }
    if (projectInfo.componentDescription) {
      projectInfo.description = projectInfo.componentDescription;
    }
    return projectInfo;
  }

  createTemplateChoice(){
    return this.template.map(item => ({
      name: item.name,
      value: item.npmName
    }))
  }
  
  isDirEmpty(localPath){
    let fileList = fs.readdirSync(localPath)
    // 文件过滤
    fileList = fileList.filter(file => ( // 不包含node_modules, .git(.开头的文件)
      !file.startsWith('.') && ['node_modules'].indexOf(file) < 0
    ))
    return !fileList || fileList.length <= 0
  }
}

function init(argv){
  console.log('8888')
  return new InitCommand(argv)
}


module.exports = init
module.exports.InitCommand = InitCommand